package com.irain.pp.tool;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Iterator;
import java.util.LinkedList;

/**
 * 通过反射获取类型所使用泛型的具体类型
 *
 * @author irain
 * @date 2017/09/16
 */
public class GenericsUtils {

    private GenericsUtils() {
    }


    public static<T,E> Class<T> getSuperGenericClass(Class<? extends E> currentClazz, Class<E> genericClazz){
        return getSuperGenericClass(currentClazz,genericClazz,0);
    }

    @SuppressWarnings("unchecked")
    public static<T,E> Class<T> getSuperGenericClass(Class<? extends E> currentClazz, Class<E> genericClazz, int index){
        Type type = getSuperGenericType(currentClazz,genericClazz,index);
        if (type instanceof Class){
            return (Class)type;
        } else if (type instanceof ParameterizedType){
            return (Class)((ParameterizedType)type).getRawType();
        } else {
            throw new IllegalArgumentException("Can't recognize this type:" + type.getClass().getName());
        }
    }

    public static<E> Type getSuperGenericType(Class<? extends E> currentClazz, Class<E> genericClazz) {
        return getSuperGenericType(currentClazz, genericClazz, 0);
    }

    public static<E> Type getSuperGenericType(Class<? extends E> currentClazz, Class<E> genericClazz, int index) {
        LinkedList<Class> classList = getClassLink(currentClazz,genericClazz);
        Iterator<Class> iterator = classList.descendingIterator();
        int i = index;
        while (iterator.hasNext()){
            Class clazz = iterator.next();
            Type type = ((ParameterizedType)clazz.getGenericSuperclass()).getActualTypeArguments()[i];
            if (type instanceof Class || type instanceof ParameterizedType) {
                return type;
            }
            i = getGenericIndex(clazz,type.getTypeName());
        }
        throw new IllegalArgumentException(currentClazz.getName() + " not implement the generic type.");
    }

    private static int getGenericIndex(Class clazz, String typeName){
        TypeVariable[] typeParameters = clazz.getTypeParameters();
        for (int i = 0,len = typeParameters.length; i < len; i++){
            if (typeName.equals(typeParameters[i].getName())){
                return i;
            }
        }
        throw new IllegalArgumentException();
    }

    private static LinkedList<Class> getClassLink(Class childClass, Class superClass){
        LinkedList<Class> classList = new LinkedList<>();
        do {
            classList.add(childClass);
        } while ((childClass = childClass.getSuperclass()) != superClass);
        return classList;
    }
}
